#include "xlx_socket.h"
#include "xlx_func.h"
#include "xlx_core.h"

#include "netdb.h"

static const char* TAG = "Socket";

// #define  EAGAIN       11  /* Try again */
//#define  EINTR         4  /* Interrupted system call */
// #define  EWOULDBLOCK  EAGAIN  /* Operation would block */

typedef struct{
    char init;
    int sock;
    TutuSocketEvent event;
    int status;
    int wait_count;
    void *userdata;
}TutuSocket_PriData;

#define MAX_SOCKET_SIZE 5
//最多建立多少个socket
static TutuSocket_PriData _pri_sockets[MAX_SOCKET_SIZE];

static int32_t tutusocket_newsocket(){
    int s = -1;
    s = socket(AF_INET,SOCK_STREAM,0);
    if(s == 0){
        s = socket(AF_INET,SOCK_STREAM,0);
    }
    return s;
}

//关闭连接
static void tutusocket_close(TutuSocketHandle handle){
    TutuSocket_PriData* data = (TutuSocket_PriData*)handle;

    if(data){
        if(data->init){
            if(data->sock != 0){
                close(data->sock);
            }
        }
        
        data->init = 0;
        data->sock = -1;
        data->status = 0;
        data->wait_count = 0;
        data->userdata = NULL;

        if(data->event.event_connect_close){
            data->event.event_connect_close(handle);
        }
    }
}

//获取一个空的可以使用的连接容器
static TutuSocket_PriData* tutusocket_get_empty(){
    int i=0;
    for(;i < MAX_SOCKET_SIZE;i++){
        if(_pri_sockets[i].init == 0){
            _pri_sockets[i].sock = -1;
            _pri_sockets[i].init = 1;
            return &(_pri_sockets[i]);
        }
    }
    return NULL;
}

//检查是否真的连接上了
static int tutusocket_check_connect(int sock){
    struct timeval timeout;
    fd_set fdset;
    int ret = 0;
    //连接服务器成功
    int connected = 1;

    //非阻塞模式下，立马连接可能会返回-1
    //需要检测通道是否可以写数据，则表示连接已经建立
    int re_connect_count = 0;
    while(1)
    {
        timeout.tv_sec = 1;
        timeout.tv_usec = 0;
        re_connect_count++;
        FD_ZERO(&fdset);
        FD_SET((unsigned int) sock, &fdset);
        ret = select(sock+1, 0, &fdset, 0, &timeout);
        if(ret > 0){
            break;
        }

        //如果10s都没连接上，则超时，按连接失败处理
        if(re_connect_count > 10){
            TUTU_LOGI(TAG, "connect failt! timeout 10s");
            connected = 0;
            break;
        }
        g_tutuos.sleep(1);
    };
    return connected;
}

//处理接收数据
static void tutusocket_recv(TutuSocket_PriData* pri){
    if(pri && pri->sock > 0){
        char buf[1024];
        int rsize = 0;
        
        do{
            //接收数据
            rsize = recv(pri->sock,buf,1024,0);

            if(rsize > 0){
                pri->wait_count = 0;
                xlx_print_hex(XLX_MAIN_SERVER_READ,buf,rsize);
                //回调，通知有数据来了
                if(pri->event.event_has_new_data){
                    pri->event.event_has_new_data(pri,buf,rsize);
                }
                
            }else if(rsize == 0){
                //服务器关闭连接
                pri->wait_count = 0;
                TUTU_LOGI(TAG, "%s  %d  server disconnect",__func__,__LINE__);
                tutusocket_close(pri);
            }else if(rsize < 0){

                if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN){
                    //说明还保持连接
                }else{
                    //说明连接可能已经坏了
                    TUTU_LOGI(TAG, "%s  %d  recv wait error=%d",__func__,__LINE__,errno);
                    //尝试等待,如果等待10次，还没收到数据则直接断开连接
                    pri->wait_count++;
                    if(pri->wait_count > 10){
                        tutusocket_close(pri);
                    }
                }
            }
        }while (rsize == 1024);
    }
}

//处理发送数据
static void tutusocket_send(TutuSocket_PriData* pri){
    if(pri && pri->sock > 0){
        if(pri->event.event_can_send_data){
            pri->event.event_can_send_data(pri);
        }
    }
}

//处理数据异常
static void tutusocket_except(TutuSocket_PriData* pri){
    if(pri){
        tutusocket_close(pri);
    }
}

//连接服务器
static TutuSocketHandle tutusocket_tcp_connect(const char *szIp,int port,TutuSocketEvent *event){
    struct sockaddr_in sin;
    int32_t s = tutusocket_newsocket();
    int code = 0;
    TutuSocket_PriData* data = NULL;

    if(s != -1){
        unsigned long on = 1;
        code = lwip_ioctl(s,FIONBIO,&on);  // 71,12:     return lwip_ioctl(fd, cmd, va_arg(args, void *));
        // code = ioctlsocket(s,FIONBIO,&on);

        if(code < 0){
            if(s != 0){
                close(s);
            }
            TUTU_LOGI(TAG, "1 connect failt %s:%d",szIp,port);
            return NULL;
        }

        memset(&sin,0,sizeof(struct sockaddr_in));
        sin.sin_family = AF_INET;
        sin.sin_port = xlx_htons(port);
        inet_pton(AF_INET,szIp,&sin.sin_addr);

        code = connect(s,(struct sockaddr*)&sin,sizeof(struct sockaddr));

        if(code <= 0){
            if(1 == tutusocket_check_connect(s)){
                TUTU_LOGI(TAG, "connect success socket=%ld %s:%d",s,szIp,port);
                //连接成功
                data = tutusocket_get_empty();
                if(data){
                    data->sock = s;
                    data->status = 1;
                    data->userdata = NULL;
                    data->wait_count = 0;
                    memset(&(data->event),0,sizeof(TutuSocketEvent));

                    if(event){
                        memcpy(&(data->event),event,sizeof(TutuSocketEvent));
                        event->event_connect_status(data,1);
                    }
                    return data;
                }else{
                    TUTU_LOGI(TAG, "tcp connect pool full!!");
                }
            }
        }
    }

    TUTU_LOGI(TAG, "2 connect failt %s:%d code=%d socket=%ld",szIp,port,code,s);

    if(s != 0){
        close(s);
    }
    
    if(event){
        event->event_connect_status(NULL,0);
    }

    return data;
}

//循环事件
static void tutusocket_loop(){
    int maxsock = 0;
    int i = 0;
    int sockid = 0;
    fd_set readfds;
    fd_set writefds;
    fd_set exceptfds;
    TutuSocket_PriData* pridata = NULL;
    struct timeval time;
    int code = 0;

    time.tv_sec = 1;
    time.tv_usec = 0;

    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    FD_ZERO(&exceptfds);

    for (i = 0; i < MAX_SOCKET_SIZE; i++)
    {
        pridata = &(_pri_sockets[i]);
        sockid = pridata->sock;

        if(sockid > 0){
            FD_SET(sockid,&readfds);
            FD_SET(sockid,&exceptfds);

            //是否有数据需要发送
            if(pridata->event.event_need_send_data && 1 == pridata->event.event_need_send_data(pridata)){
                FD_SET(sockid,&writefds);
            }
        }

        if(sockid > maxsock){
            maxsock = sockid;
        }
    }

    if(maxsock <= 0){
        return;
    }

    //查询内核事件
    code = select(maxsock+1,&readfds,&writefds,&exceptfds,&time);

    //TUTU_LOGI(TAG, "----------------------poll-----------------%d----------",code);

    if(code < 0){
        //出错了
    }else if (code > 0){
        for (i = 0; i < MAX_SOCKET_SIZE; i++){
            pridata = &(_pri_sockets[i]);
            sockid = pridata->sock;

            if(sockid > 0){
                if(FD_ISSET(sockid,&readfds)){
                    tutusocket_recv(pridata);
                }

                if(FD_ISSET(sockid,&writefds)){
                    tutusocket_send(pridata);
                }

                if(FD_ISSET(sockid,&exceptfds)){
                    tutusocket_except(pridata);
                }
            }
        }
    }else{
        //超时
    }
}


//设置用户数据
static void tutusocket_set_user_data(TutuSocketHandle handle,void *data){
    TutuSocket_PriData *p = (TutuSocket_PriData*)handle;
    p->userdata = data;
}

    //获取用户数据
static void* tutusocket_get_user_data(TutuSocketHandle handle){
    TutuSocket_PriData *p = (TutuSocket_PriData*)handle;
    return p->userdata;
}

//发送数据
static int tutusocket_socket_write(TutuSocketHandle handle,char *data,int len){
    int wsize = 0;
    TutuSocket_PriData *p = (TutuSocket_PriData*)handle;
    wsize = send(p->sock,data,len,0);

    if(wsize == 0){     //服务器已经关闭连接
        TUTU_LOGI(TAG, "Socket send data[%d] failed, error=%d", len, errno);
        tutusocket_close(handle);
    }else if(wsize < 0){
        if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN){
            //发送缓冲区已经满，没发出去
            return 0;
        }else{
            TUTU_LOGI(TAG, "%s %d send socket data failt!!error=%d",__func__,__LINE__,errno);
            tutusocket_close(handle);
            return 0;
        }
    }

    if (wsize > 0) {
        xlx_print_hex(XLX_MAIN_SERVER_WRITE,data,wsize);
    }
    return wsize;//实际发送出去的数据量
}


//初始化
void TutuSocket_Init(TutuSocket *myself){
    TUTU_LOGI(TAG, "%s(%d)", __FUNCTION__, __LINE__); 
    memset(_pri_sockets,0,sizeof(_pri_sockets));
    myself->socket_close = tutusocket_close;
    myself->socket_loop = tutusocket_loop;
    myself->tcp_connect = tutusocket_tcp_connect;
    myself->set_user_data = tutusocket_set_user_data;
    myself->get_user_data = tutusocket_get_user_data;
    myself->socket_write = tutusocket_socket_write;
    TUTU_LOGI(TAG, "sys_init: %s(%d)", __FUNCTION__, __LINE__);    
}